#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/time.h>
#include "threadpool.h"

#define true 1
#define false 0

typedef struct
{
    void *(*function)(void *);
    void *arg;
} threadpool_task_t;

struct threadpool_t
{
    pthread_mutex_t lock; /* 用于锁住本结构体 */
    pthread_mutex_t thread_counter;

    pthread_cond_t queue_not_full;  /* 当任务队列满时，添加任务的线程阻塞，等待此条件变量 */
    pthread_cond_t queue_not_empty; /* 任务队列里不为空时，通知等待任务的线程 */
    pthread_cond_t queue_is_empty;
    pthread_cond_t task_done;

    pthread_t *threads;            /* 存放线程池中每个线程的tid。数组 */
    threadpool_task_t *task_queue; /* 任务队列(数组首地址) */

    int thr_num; /* 当前存活线程个数 */

    int queue_rear;     /* task_queue队尾下标 */
    int queue_size;     /* task_queue队中实际任务数 */
    int queue_max_size; /* task_queue队列可容纳任务数上限 */
    int queue_work;
    int task_done_num;

    int shutdown; /* 标志位，线程池使用状态，true或false */
};

void *threadpool_thread(void *threadpool);

int threadpool_free(threadpool_t *pool);

threadpool_t *threadpool_create(int thr_num, int queue_size)
{
    threadpool_t *pool = NULL;

    do
    {
        if ((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL)
        {
            printf("malloc threadpool fail");
            break;
        }

        pool->thr_num = thr_num;
        pool->queue_size = 0;
        pool->queue_max_size = queue_size;
        pool->queue_rear = 0;
        pool->queue_work = 0;
        pool->task_done_num = 0;
        pool->shutdown = false;

        int qsize = pool->queue_max_size;
        pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thr_num);
        if (pool->threads == NULL)
        {
            printf("malloc threads fail");
            break;
        }
        memset(pool->threads, 0, sizeof(pthread_t) * thr_num);

        pool->task_queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t) * qsize);
        if (pool->task_queue == NULL)
        {
            printf("malloc task_queue fail");
            break;
        }

        if (pthread_mutex_init(&(pool->lock), NULL) != 0 ||
            pthread_mutex_init(&(pool->thread_counter), NULL) != 0 ||
            pthread_cond_init(&(pool->queue_not_empty), NULL) != 0 ||
            pthread_cond_init(&(pool->queue_is_empty), NULL) != 0 ||
            pthread_cond_init(&(pool->task_done), NULL) != 0 ||
            pthread_cond_init(&(pool->queue_not_full), NULL) != 0)
        {
            printf("init the lock or cond fail");
            break;
        }

        int i;
        for (i = 0; i < thr_num; i++)
        {
            pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool); /*pool指向当前线程池*/
            // printf("start thread 0x%x...\n", (unsigned int)pool->threads[i]);
        }

        return pool;

    } while (0);

    threadpool_free(pool);

    return NULL;
}

int threadpool_add(threadpool_t *pool, void *(*function)(void *arg), void *arg)
{
    pthread_mutex_lock(&(pool->lock));

    while ((pool->queue_size == pool->queue_max_size) && (!pool->shutdown))
    {
        pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
    }

    if (pool->shutdown)
    {
        pthread_cond_broadcast(&(pool->queue_not_empty));
        pthread_mutex_unlock(&(pool->lock));
        return 0;
    }

    if (pool->task_queue[pool->queue_rear].arg != NULL)
    {
        pool->task_queue[pool->queue_rear].arg = NULL;
    }

    pool->task_queue[pool->queue_rear].function = function;
    pool->task_queue[pool->queue_rear].arg = arg;

    pool->queue_rear = (pool->queue_rear + 1) % pool->queue_max_size;
    pool->queue_size++;

    pthread_cond_signal(&(pool->queue_not_empty));
    pthread_mutex_unlock(&(pool->lock));

    return 0;
}

void *threadpool_thread(void *threadpool)
{
    threadpool_t *pool = (threadpool_t *)threadpool;
    threadpool_task_t task;

    while (true)
    {
        pthread_mutex_lock(&(pool->lock));

        while ((pool->queue_size == 0) && (!pool->shutdown))
        {
            // printf("thread 0x%x is waiting\n", (unsigned int)pthread_self());
            pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
        }

        if (pool->shutdown)
        {
            pthread_mutex_unlock(&(pool->lock));
            // printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
            // pthread_detach(pthread_self());
            pthread_exit(NULL);
        }

        task.function = pool->task_queue[pool->queue_work].function;
        task.arg = pool->task_queue[pool->queue_work].arg;

        pool->queue_work = (pool->queue_work + 1) % pool->queue_max_size;

        pool->queue_size--;
        if (pool->queue_size == 0)
        {
            pthread_cond_signal(&(pool->queue_is_empty));
        }

        pthread_cond_signal(&(pool->queue_not_full));

        pthread_mutex_unlock(&(pool->lock));

        // printf("thread 0x%x start working\n", (unsigned int)pthread_self());

        (*(task.function))(task.arg);

        pthread_mutex_lock(&(pool->thread_counter));
        pool->task_done_num++;
        pthread_mutex_unlock(&(pool->thread_counter));

        if (pool->queue_size == 0)
            pthread_cond_signal(&(pool->task_done));

        // printf("thread 0x%x end working\n", (unsigned int)pthread_self());
    }

    pthread_exit(NULL);
}

int threadpool_destroy(threadpool_t *pool)
{
    int i;
    if (pool == NULL)
    {
        return -1;
    }
    pthread_mutex_lock(&(pool->lock));
    while (pool->queue_size != 0)
    {
        pthread_cond_wait(&(pool->queue_is_empty), &(pool->lock));
    }
    pthread_mutex_unlock(&(pool->lock));
    pool->shutdown = true;

    for (i = 0; i < pool->thr_num; i++)
    {
        pthread_cond_signal(&(pool->queue_not_empty));
    }
    for (i = 0; i < pool->thr_num; i++)
    {
        pthread_join(pool->threads[i], NULL);
    }
    threadpool_free(pool);

    return 0;
}

int wait_threadpool(threadpool_t *pool, int work_num)
{
    if (pool == NULL)
    {
        return -1;
    }
    pthread_mutex_lock(&(pool->thread_counter));

    while (pool->task_done_num != work_num)
    {
        pthread_cond_wait(&(pool->task_done), &(pool->thread_counter));
    }
    pthread_mutex_unlock(&(pool->thread_counter));

    assert(pool->queue_size == 0);
    assert(pool->task_done_num == work_num);
    pool->task_done_num = 0;
    return 0;
}

int threadpool_free(threadpool_t *pool)
{
    if (pool == NULL)
    {
        return -1;
    }

    if (pool->task_queue)
    {
        free(pool->task_queue);
    }
    if (pool->threads)
    {
        free(pool->threads);
        pthread_mutex_lock(&(pool->lock));
        pthread_mutex_destroy(&(pool->lock));
        pthread_mutex_lock(&(pool->thread_counter));
        pthread_mutex_destroy(&(pool->thread_counter));
        pthread_cond_destroy(&(pool->queue_not_empty));
        pthread_cond_destroy(&(pool->queue_is_empty));
        pthread_cond_destroy(&(pool->task_done));
        pthread_cond_destroy(&(pool->queue_not_full));
    }
    free(pool);
    pool = NULL;

    return 0;
}
